Reflection trong Java
Java Reflection là một API mạnh mẽ cho phép chương trình truy cập, kiểm tra và thao tác với cấu trúc của các lớp (classes), các phương thức (methods), các trường (fields) và constructor của đối tượng ngay cả khi không biết trước thông tin cụ thể về chúng trong thời gian biên dịch. Điều này mở ra nhiều khả năng như xây dựng các framework, thư viện, công cụ kiểm thử, hay các hệ thống plugin động, tuy nhiên cũng cần cẩn trọng vì Reflection có thể làm giảm tính an toàn về kiểu (type-safety) và hiệu năng.
Bài viết dưới đây sẽ trình bày chi tiết các implementation và các method của Reflection trong Java, kèm theo các ví dụ thực tế cụ thể cho từng nội dung.
1. Lấy Đối Tượng Class
1.1. Sử Dụng .class
Cách đơn giản nhất để lấy đối tượng Class của một lớp cụ thể là sử dụng cú pháp TênLớp.class.
Class<String> clazz = String.class;
System.out.println("Class name: " + clazz.getName());
1.2. Sử Dụng getClass()
Khi đã có một đối tượng, bạn có thể lấy đối tượng Class của nó bằng cách gọi phương thức getClass().
String str = "Hello Reflection";
Class<?> clazz = str.getClass();
System.out.println("Class name: " + clazz.getName());
1.3. Sử Dụng Class.forName(String className)
Phương thức tĩnh này cho phép tải lớp theo tên đầy đủ (fully qualified name) trong thời gian chạy.
try {
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("Class name: " + clazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2. Constructor – Khởi Tạo Đối Tượng Qua Reflection
2.1. Lấy Constructor
Bạn có thể lấy các constructor của lớp bằng các phương thức như getConstructor() (cho public constructor) hoặc getDeclaredConstructor() (cho tất cả, kể cả private).
public class Person {
private String name;
private int age;
public Person() {
this.name = "Unknown";
this.age = 0;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters ...
}
Ví dụ sử dụng Reflection để tạo đối tượng Person:
try {
Class<Person> personClass = Person.class;
// Lấy constructor không đối số (public)
Constructor<Person> defaultConstructor = personClass.getConstructor();
Person person1 = defaultConstructor.newInstance();
System.out.println("Person1: " + person1.getName() + ", " + person1.getAge());
// Lấy constructor có đối số
Constructor<Person> parameterizedConstructor = personClass.getConstructor(String.class, int.class);
Person person2 = parameterizedConstructor.newInstance("Alice", 30);
System.out.println("Person2: " + person2.getName() + ", " + person2.getAge());
} catch (Exception e) {
e.printStackTrace();
}
2.2. Constructor Private
Bạn có thể truy cập constructor private bằng cách sử dụng setAccessible(true):
public class Secret {
private String message;
private Secret(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
try {
Class<Secret> secretClass = Secret.class;
Constructor<Secret> privateConstructor = secretClass.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true); // Cho phép truy cập đến constructor private
Secret secret = privateConstructor.newInstance("Reflection mở khóa private!");
System.out.println("Secret message: " + secret.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
3. Truy Cập và Thay Đổi Trường (Fields)
3.1. Lấy Field
- getField(String name): Lấy public field.
- getDeclaredField(String name): Lấy field khai báo trong lớp (bao gồm private, protected, default).
Ví dụ:
public class Car {
public String model;
private int year;
public Car(String model, int year) {
this.model = model;
this.year = year;
}
}
Truy cập field:
try {
Car car = new Car("Toyota", 2020);
Class<?> carClass = car.getClass();
// Truy cập field public
Field modelField = carClass.getField("model");
System.out.println("Model: " + modelField.get(car));
// Truy cập field private
Field yearField = carClass.getDeclaredField("year");
yearField.setAccessible(true); // Cho phép truy cập private
System.out.println("Year: " + yearField.getInt(car));
// Thay đổi giá trị của field
modelField.set(car, "Honda");
yearField.set(car, 2021);
System.out.println("After change - Model: " + car.model + ", Year: " + carClass.getDeclaredField("year").get(car));
} catch (Exception e) {
e.printStackTrace();
}
3.2. Lấy Danh Sách Các Field
Bạn có thể lấy tất cả các field của lớp bằng:
getFields(): Lấy tất cả các field public, bao gồm cả của lớp cha.getDeclaredFields(): Lấy tất cả các field được khai báo trong lớp (không bao gồm của lớp cha).
Field[] fields = Car.class.getDeclaredFields();
for (Field field : fields) {
System.out.println("Field: " + field.getName());
}
4. Truy Cập và Gọi Phương Thức (Methods)
4.1. Lấy Method
getMethod(String name, Class<?>... parameterTypes):Lấy public method (bao gồm cả của lớp cha).getDeclaredMethod(String name, Class<?>... parameterTypes):Lấy method được khai báo trong lớp, kể cả private.
Ví dụ:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
private int multiply(int a, int b) {
return a * b;
}
}
Gọi phương thức:
try {
Calculator calc = new Calculator();
Class<?> calcClass = calc.getClass();
// Gọi public method add
Method addMethod = calcClass.getMethod("add", int.class, int.class);
int sum = (int) addMethod.invoke(calc, 5, 3);
System.out.println("5 + 3 = " + sum);
// Gọi private method multiply
Method multiplyMethod = calcClass.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);
int product = (int) multiplyMethod.invoke(calc, 5, 3);
System.out.println("5 * 3 = " + product);
} catch (Exception e) {
e.printStackTrace();
}
4.2. Lấy Danh Sách Các Method
Tương tự như với field, bạn có thể lấy tất cả các method:
getMethods(): Lấy tất cả các public method (cả của lớp cha).getDeclaredMethods(): Lấy tất cả các method được khai báo trong lớp.
Method[] methods = Calculator.class.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
5. Reflection và Annotation
Reflection cũng có thể được sử dụng để đọc các annotation của lớp, field, method, hay constructor.
Giả sử có annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
String value() default "";
}
Và lớp sử dụng annotation:
public class MyTests {
@Test("Unit test for addition")
public void testAdd() {
// Test logic...
}
}
Sử dụng Reflection để kiểm tra annotation:
Method[] testMethods = MyTests.class.getDeclaredMethods();
for (Method method : testMethods) {
if (method.isAnnotationPresent(Test.class)) {
Test testAnnotation = method.getAnnotation(Test.class);
System.out.println("Method " + method.getName() + " có annotation @Test với giá trị: " + testAnnotation.value());
}
}
6. Các Lưu Ý Khi Sử Dụng Reflection
- Hiệu năng: Reflection có thể chậm hơn so với truy cập trực tiếp vì các thao tác truy xuất thông tin kiểu động.
- Bảo mật: Reflection có thể phá vỡ tính đóng gói (encapsulation) của OOP, do đó nên sử dụng cẩn thận.
- An toàn kiểu: Sử dụng Reflection sẽ mất đi một số đảm bảo về kiểu dữ liệu trong thời gian biên dịch, do đó lỗi có thể xảy ra chỉ khi chạy (runtime).
7. Tổng Kết
Java Reflection cung cấp một bộ công cụ linh hoạt cho phép:
- Lấy đối tượng Class: Thông qua
.class,getClass(), vàClass.forName(). - Khởi tạo đối tượng: Qua việc lấy và gọi constructor (bao gồm cả private nếu cần).
- Truy cập và thay đổi các field: Cả public và private.
- Gọi phương thức: Với các method public và private, cũng như kiểm tra annotation.
- Lấy danh sách các thành phần: Các field, method, constructor của một lớp.
Nhờ vào Reflection, các framework như Spring, Hibernate và các thư viện kiểm thử có thể kiểm soát các đối tượng một cách động, tạo ra các giải pháp tổng quát cho nhiều vấn đề lập trình. Tuy nhiên, cần sử dụng một cách thận trọng để tránh làm mất đi tính an toàn và hiệu năng của ứng dụng.
Mỗi ví dụ trên minh họa cho các khả năng cụ thể của Reflection, từ việc khởi tạo đối tượng, truy cập và thay đổi giá trị của trường, cho tới việc gọi các phương thức và xử lý annotation. Qua đó, bạn có thể hiểu rõ hơn về sức mạnh cũng như những rủi ro khi sử dụng Reflection trong Java.